package com.eight.cloud.redisson.utils;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.eight.cloud.core.global.model.LoginUser;
import com.eight.cloud.core.global.model.SessionInfo;
import com.eight.cloud.core.utils.jwt.JwtTokenUtil;
import io.jsonwebtoken.Claims;
import lombok.Data;
import org.redisson.client.protocol.ScoredEntry;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.MessageFormat;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

import static com.eight.cloud.core.global.constant.NumConstant.ONE;
import static com.eight.cloud.core.global.constant.NumConstant.ZERO;
import static com.eight.cloud.core.global.constant.StrConstant.*;


/**
 * @ClassName: TokenManager
 * @Author: TXC
 * @Date: 2023-07-30 16:30
 **/
@AutoConfiguration
@ConfigurationProperties(prefix = "security.token")
@Data
@RefreshScope
public class TokenManager {
    // 设置token私钥
    private String secret;
    // 设置token过期时间
    private long expire = 3600;

    private final String userinfoCacheKey = "auth:token:userinfo:{0}";
    private final String sessionCacheKey = "auth:token:session:{0}:{1}";
    private final String userSessionCacheKey = "auth:token:user_sessions:{0}";
    @Resource
    private RedisUtils redisUtils;

    private String formatUserinfoCacheKey(String userId) {
        return MessageFormat.format(this.userinfoCacheKey, userId);
    }

    private String formatSessionCacheKey(String userId, String sessionId) {
        return MessageFormat.format(this.sessionCacheKey, userId, sessionId);
    }

    private String formatUserSessionCacheKey(String userId) {
        return MessageFormat.format(this.userSessionCacheKey, userId);
    }

    /**
     * 创建token
     *
     * @param username 用户名
     * @return java.lang.String
     */
    public String createToken(String username, String userId) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put(USERNAME, username);
        claims.put(USER_ID, userId);
        claims.put(SESSION_ID, generateUUIDToken(true));
        return JwtTokenUtil.createToken(secret, claims, expire);
    }

    /**
     * 生成 AccessToken
     */
    public static String generateUUIDToken(boolean isSimple) {
        return UUID.fastUUID().toString(isSimple);
    }

    /**
     * 解析token
     *
     * @param token token
     * @return io.jsonwebtoken.Claims
     */
    public Claims parserToken(String token) {
        return JwtTokenUtil.parserToken(token, secret);
    }

    /**
     * 刷新token
     *
     * @param token token
     * @return java.lang.String
     */
    public String refreshToken(String token) {
        return JwtTokenUtil.createToken(secret, JwtTokenUtil.parserToken(token, secret), expire);
    }

    /**
     * token是否过期
     *
     * @param token token
     * @return boolean
     */
    public boolean isExpiration(String token) {
        return isExpiration(token, 0);
    }

    /**
     * token是否在指定时间后过期
     * @param token token
     * @param expire 指定时间（毫秒）
     * @return boolean
     */
    public boolean isExpiration(String token, long expire) {
        return isExpiration(token, System.currentTimeMillis(), expire);
    }

    /**
     * token是否在指定时间后过期
     * @param token token
     * @param expire 指定时间（毫秒）
     * @param now 当前时间（毫秒）
     * @return boolean
     */
    public boolean isExpiration(String token, long expire, long now) {
        return JwtTokenUtil.parserToken(token, secret).getExpiration().before(new Date(now + expire));
    }

    /**
     * 通过token获取用户名
     *
     * @param token token
     * @return java.lang.String
     */
    public String getUsername(String token) {
        return parserToken(token).get(USERNAME).toString();
    }

    /**
     * 获取用户名
     *
     * @return java.lang.String
     */
    public String getUsername(HttpServletRequest request) {
        return getUsername(getAccessToken(request));
    }

    /**
     * 通过token获取用户名
     *
     * @param token token
     * @return java.lang.String
     */
    public String getUserId(String token) {
        return parserToken(token).get(USER_ID).toString();
    }

    /**
     * 通过token获取用户名
     *
     * @param token token
     * @return java.lang.String
     */
    public String getSessionId(String token) {
        return parserToken(token).get(SESSION_ID).toString();
    }

    /**
     * 通过token获TokenId
     *
     * @param token token
     * @return java.lang.String
     */
    public String getTokenId(String token) {
        return parserToken(token).get(TOKEN_ID).toString();
    }

    /**
     * 通过token获TokenId
     *
     * @param request http请求
     * @return java.lang.String
     */
    public String getTokenId(HttpServletRequest request) {
        return getTokenId(getAccessToken(request));
    }

    /**
     * 获取用户名
     *
     * @return java.lang.String
     */
    public String getUserId(HttpServletRequest request) {
        return getUserId(getAccessToken(request));
    }

    /**
     * 获取 AccessToken
     */
    public String getAccessToken(HttpServletRequest request) {
        String accessToken = request.getHeader(AUTHORIZATION);
        if (StrUtil.isBlank(accessToken)) {
            accessToken = request.getParameter(TOKEN);
        }
        return accessToken;
    }

    /**
     * 获取登录用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        String token = getAccessToken(request);
        return getLoginUser(token);
    }

    /**
     * 获取登录用户信息
     */
    public LoginUser getLoginUser(String token) {
        try {
            SessionInfo sessionInfo = (SessionInfo) redisUtils.get(formatSessionCacheKey(getUserId(token), getSessionId(token)));
            if (ObjectUtil.isNotNull(sessionInfo)) {
                return (LoginUser) redisUtils.get(formatUserinfoCacheKey(getUserId(token)));
            }
            // 删除用户会话列表中的无效会话
            redisUtils.scoredSortedSetRemove(formatUserSessionCacheKey(getUserId(token)), getSessionId(token));
            return null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 缓存用户信息
     *
     * @param token     token
     * @param loginUser loginUser
     */
    public void setLoginUser(String token, LoginUser loginUser, Integer members) {
        String userId = getUserId(token);
        String sessionId = getSessionId(token);
        // 设置token有效期
        long expireTimestamp = Instant.now().getEpochSecond() + getExpire();
        // 用户基本信息单独存储（所有会话共享）
        redisUtils.set(formatUserinfoCacheKey(userId), loginUser);
        // 存储用户会话列表
        redisUtils.scoredSortedSetAdd(formatUserSessionCacheKey(userId), sessionId, expireTimestamp);
        setLoginUserExpire(token, expireTimestamp);
        crowdOutLastSession(userId, members);
    }

    /**
     * 设置用户信息有效期
     * @param token token
     * @param expireTimestamp 过期时间戳
     */
    public void setLoginUserExpire(String token, Long expireTimestamp) {
        String userId = getUserId(token);
        String sessionId = getSessionId(token);
        // 设置用户信息有效期
        redisUtils.setExpireAtSeconds(formatUserinfoCacheKey(userId), expireTimestamp);
        // 会话元数据存储
        SessionInfo session = new SessionInfo(sessionId, userId, expireTimestamp);
        redisUtils.set(formatSessionCacheKey(userId, sessionId), session);
        // 设置会话有效期
        redisUtils.setExpireAtSeconds(formatSessionCacheKey(userId, sessionId), expireTimestamp);
        // 跟新用户会话列表有效期
        redisUtils.scoredSortedSetExpireAtSecond(formatUserSessionCacheKey(userId), expireTimestamp);
    }

    /**
     * 挤掉最后早一个登陆用户
     */
    public void crowdOutLastSession(String userId, Integer members) {
        // 查询当前用户会话数
        int length = redisUtils.scoredSortedSetSize(formatUserSessionCacheKey(userId));
        // 如果会话数大于最大会话数，则挤掉最后一个会话
        if (length > members) {
            Map<Object, Double> collect = redisUtils.entryRangeReversed(formatUserSessionCacheKey(userId), members,
                            length - ONE)
                    .stream().collect(Collectors.toMap(ScoredEntry::getValue, ScoredEntry::getScore));
            collect.forEach((sessionId, scored) -> delLoginUserSinglePoint(userId, String.valueOf(sessionId)));
        }
    }

    /**
     * 删除单点登录用户信息
     *
     * @param token token
     */
    public void delLoginUserSinglePoint(String token) {
        delLoginUserSinglePoint(getUserId(token), getSessionId(token));
    }

    /**
     * 删除单点登录用户信息
     *
     * @param userId    userId
     * @param sessionId sessionId
     */
    public void delLoginUserSinglePoint(String userId, String sessionId) {
        // 从用户会话列表中删除会话
        redisUtils.scoredSortedSetRemove(formatUserSessionCacheKey(userId), sessionId);
        // 删除会话信息
        redisUtils.delete(formatSessionCacheKey(userId, sessionId));
        int length = redisUtils.scoredSortedSetSize(formatUserSessionCacheKey(userId));
        if (length == ZERO) {
            // 如果会话列表为空，则删除用户信息
            redisUtils.delete(formatUserinfoCacheKey(userId));
        }
    }

    /**
     * 剔除下线
     *
     * @param userId 用户id
     */
    public void removeLoginUserMulti(String userId) {
        // 删除所有会话信息
        redisUtils.scoredSortedSetReadAll(formatUserSessionCacheKey(userId)).forEach(sessionId -> {
            redisUtils.delete(formatSessionCacheKey(userId, String.valueOf(sessionId)));
        });
        // 清空用户会话列表
        redisUtils.scoredSortedSetClear(formatUserSessionCacheKey(userId));
        // 删除用户信息
        redisUtils.delete(formatUserinfoCacheKey(userId));
    }

    /**
     * 删除多端登录用户信息
     *
     * @param token 用户id
     */
    public void delLoginUserMulti(String token) {
        // 使用事务删除用户信息
        removeLoginUserMulti(getUserId(token));
    }
}
